#include "Wt.h"

/* WtCollection: A list of objects with convenient Tcl accessor functions */

/* Extract the underlying collection from a Tcl object */

WtCollection *WtToCln(Tcl_Obj *obj)
{
  return (WtCollection *)(obj->internalRep.twoPtrValue.ptr1);
}

/* Create a new collection object */

Tcl_Obj *WtNewClnObj(WtClnItemType *itemType)
{
  WtCollection *cln;
  Tcl_Obj *obj;

  cln = (WtCollection *)ckalloc(sizeof(WtCollection));
  cln->itemType = itemType;
  cln->list = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(cln->list);

  obj = Tcl_NewObj();
  obj->internalRep.twoPtrValue.ptr1 = cln;
  obj->typePtr = &WtClnType;
  Tcl_InvalidateStringRep(obj);

  return obj;
}

/* Get the size of a collection */

int WtClnSize(Tcl_Obj *cln)
{
  WtCollection *clnRep = WtToCln(cln);
  return WtGetListSize(clnRep->list);
}

/* Get a collection item */

Tcl_Obj *WtGetClnItemAtIndex(Tcl_Obj *cln, int idx)
{
  WtCollection *clnRep = WtToCln(cln);
  return WtGetListItem(clnRep->list, idx);
}

/* Set a collection item */

void SetClnItemAtIndex(Tcl_Obj *cln, int idx, Tcl_Obj *obj)
{
  WtCollection *clnRep = WtToCln(cln);
  WtReplaceInList(clnRep->list, idx, 1, 1, &obj);
}

/* Add an item to a collection */

void WtAppendClnItem(Tcl_Obj *cln, Tcl_Obj *obj)
{
  WtCollection *clnRep = WtToCln(cln);
  WtAppendToList(clnRep->list, obj);
}

/* Add a list of items to a collection */

void WtAppendAllClnItems(Tcl_Obj *cln, Tcl_Obj *list)
{
  int len, i;
  Tcl_Obj *val;

  len = WtGetListSize(list);

  for (i = 0; i < len; i++) {
    val = WtGetListItem(list, i);
    WtAppendClnItem(cln, val);
  }
}

/* Compare an item to a search key */

int WtCompareClnItem(Tcl_Obj *cln, Tcl_Obj *item, Tcl_Obj *key)
{
  WtCollection *clnRep = WtToCln(cln);
  char *itemStr, *keyStr;

  if (item == key) {
    return 0;
  }

  if (clnRep->itemType && clnRep->itemType->compare) {
    return clnRep->itemType->compare(item, key);
  }

  itemStr = WtToString(item);
  keyStr = WtToString(key);
  return strcmp(itemStr, keyStr);
}

/* Extract the key from an item */

Tcl_Obj *WtGetClnItemKey(Tcl_Obj *cln, Tcl_Obj *item)
{
  WtCollection *clnRep = WtToCln(cln);
  if (clnRep->itemType && clnRep->itemType->getKey) {
    return clnRep->itemType->getKey(item);
  }
  return item;
}

/* Extract the value from an item */

Tcl_Obj *WtGetClnItemValue(Tcl_Obj *cln, Tcl_Obj *item)
{
  WtCollection *clnRep = WtToCln(cln);
  if (clnRep->itemType && clnRep->itemType->getValue) {
    return clnRep->itemType->getValue(item);
  }
  return item;
}

/* Get all the items */

Tcl_Obj *WtGetClnItems(Tcl_Obj *cln)
{
  return Tcl_DuplicateObj(WtToCln(cln)->list);
}

/* Get all item keys */

Tcl_Obj *WtGetClnKeys(Tcl_Obj *cln)
{
  Tcl_Obj *list = Tcl_NewListObj(0, NULL);
  Tcl_Obj *item, *obj;
  int i;

  for (i = 0; i < WtClnSize(cln); i++) {
    item = WtGetClnItemAtIndex(cln, i);
    obj = WtGetClnItemKey(cln, item);
    WtAppendToList(list, obj);
  }

  return list;
}

/* Get all item values */

Tcl_Obj *WtGetClnValues(Tcl_Obj *cln)
{
  Tcl_Obj *list = Tcl_NewListObj(0, NULL);
  Tcl_Obj *item, *obj;
  int i;

  for (i = 0; i < WtClnSize(cln); i++) {
    item = WtGetClnItemAtIndex(cln, i);
    obj = WtGetClnItemValue(cln, item);
    WtAppendToList(list, obj);
  }

  return list;
}

/* Find an item using a key */

Tcl_Obj *WtFindClnItemIndex(Tcl_Obj *cln, Tcl_Obj *key, int start, int *index)
{
  Tcl_Obj *item;
  int i;

  for (i = start; i < WtClnSize(cln); i++) {
    item = WtGetClnItemAtIndex(cln, i);
    if (WtCompareClnItem(cln, item, key) == 0) {
      if (index) {
        *index = i;
      }
      return item;
    }
  }

  if (index) {
    *index = -1;
  }
  return NULL;
}

/* Find an item in reverse order using a key */

Tcl_Obj *WtFindClnItemIndexReverse(Tcl_Obj *cln, Tcl_Obj *key, int start, int *index)
{
  Tcl_Obj *item;
  int i;

  if (start == -1) {
    start = WtClnSize(cln);
  }

  for (i = start; i >= 0; i--) {
    item = WtGetClnItemAtIndex(cln, i);
    if (WtCompareClnItem(cln, item, key) == 0) {
      if (index) {
        *index = i;
      }
      return item;
    }
  }

  if (index) {
    *index = -1;
  }
  return NULL;
}

/* Find the first matching item */

Tcl_Obj *WtFindClnItem(Tcl_Obj *cln, Tcl_Obj *key)
{
  return WtFindClnItemIndex(cln, key, 0, NULL);
}

/* Get all items matching the key */

Tcl_Obj *WtFindAllClnItems(Tcl_Obj *cln, Tcl_Obj *key)
{
  Tcl_Obj *list = Tcl_NewListObj(0, NULL);
  Tcl_Obj *item;
  int start = 0;

  for (;;) {
    item = WtFindClnItemIndex(cln, key, start, &start);
    if (!item) {
      break;
    }
    WtAppendToList(list, item);
  }

  return list;
}

/* Extract the key from all items that match the search key */

Tcl_Obj *WtFindAllClnKeys(Tcl_Obj *cln, Tcl_Obj *key)
{
  Tcl_Obj *list = Tcl_NewListObj(0, NULL), *item, *itemKey;
  int start = 0;

  for (;;) {
    item = WtFindClnItemIndex(cln, key, start, &start);
    if (!item) {
      break;
    }
    itemKey = WtGetClnItemKey(cln, item);
    WtAppendToList(list, itemKey);
  }

  return list;
}

/* Extract the value from all items that match the search key */

Tcl_Obj *WtFindAllClnValues(Tcl_Obj *cln, Tcl_Obj *key)
{
  Tcl_Obj *list = Tcl_NewListObj(0, NULL);
  Tcl_Obj *item, *val;
  int start = 0;

  for (;;) {
    item = WtFindClnItemIndex(cln, key, start, &start);
    if (!item) {
      break;
    }
    val = WtGetClnItemKey(cln, item);
    WtAppendToList(list, val);
  }

  return list;
}

/* Remove a collection item */

void WtRemoveFromCln(Tcl_Obj *cln, Tcl_Obj *target, int limit)
{
  Tcl_Obj *item;
  WtCollection *clnRep = WtToCln(cln);
  int i, count = 0;

  if (limit == 0) {
    return;
  }

  for (i = WtClnSize(cln); i >= 0; i--) {
    item = WtGetClnItemAtIndex(cln, i);
    if (item == target) {
      WtReplaceInList(clnRep->list, i, 1, 0, NULL);
      if (++count == limit && limit != -1) {
        break;
      }
    }
  }
}

/* Remove a collection item at an index */

void RemoveFromClnByIndex(Tcl_Obj *cln, int idx)
{
  WtCollection *clnRep = WtToCln(cln);
  WtReplaceInList(clnRep->list, idx, 1, 0, NULL);
}

/* Remove a collection item by key */

void WtRemoveFromClnByKey(Tcl_Obj *cln, Tcl_Obj *key, int limit)
{
  Tcl_Obj *item;
  WtCollection *clnRep = WtToCln(cln);
  int i, count = 0;

  if (limit == 0) {
    return;
  }

  for (i = WtClnSize(cln); i >= 0; i--) {
    item = WtGetClnItemAtIndex(cln, i);
    if (WtCompareClnItem(cln, item, key) == 0) {
      WtReplaceInList(clnRep->list, i, 1, 0, NULL);
      if (++count == limit && limit != -1) {
        break;
      }
    }
  }
}

/* Remove a list of collection items by key */

void WtRemoveFromClnByKeyList(Tcl_Obj *cln, Tcl_Obj *list, int limit)
{
  Tcl_Obj *item, *key;
  int i, j, count = 0, len;

  if (limit == 0) {
    return;
  }

  len = WtGetListSize(list);

  for (i = WtClnSize(cln); i >= 0; i--) {
    item = WtGetClnItemAtIndex(cln, i);
    for (j = 0; j < len; j++) {
      key = WtGetListItem(list, i);
      if (WtCompareClnItem(cln, item, key) == 0) {
        WtReplaceInList(list, i, 1, 0, NULL);
        ++count;
        break;
      }
    }
    if (count == limit && limit != -1) {
      break;
    }
  }
}

/* Clear the contents of a collection */

void WtClearCollection(Tcl_Obj *cln)
{
  WtCollection *clnRep = WtToCln(cln);
  Tcl_DecrRefCount(clnRep->list);
  clnRep->list = Tcl_NewListObj(0, NULL);
  Tcl_IncrRefCount(clnRep->list);
}

/* Tcl object procs */

static void WtClnFree(Tcl_Obj *cln)
{
  WtCollection *clnRep = WtToCln(cln);
  WtDeletePtr(cln);
  Tcl_DecrRefCount(clnRep->list);
  ckfree((char *)clnRep);
}

static void WtClnDup(Tcl_Obj *src, Tcl_Obj *dst)
{
  WtCollection *srcRep = WtToCln(src);
  WtCollection *dstRep = (WtCollection *)ckalloc(sizeof(WtCollection));
  dstRep->list = Tcl_DuplicateObj(srcRep->list);
  Tcl_IncrRefCount(dstRep->list);
  dstRep->itemType = srcRep->itemType;
  dst->typePtr = &WtClnType;
  dst->internalRep.twoPtrValue.ptr1 = dstRep;
}

static void WtClnUpdateString(Tcl_Obj *cln)
{
  WtUpdatePtrString(cln);
}

static int WtClnSetFromAny(Tcl_Interp *interp, Tcl_Obj *obj)
{
  return WtSetPtrFromAny(interp, obj, &WtClnType);
}

/* Collection object type */

struct Tcl_ObjType WtClnType =
{
  "collection",
  WtClnFree,
  WtClnDup,
  WtClnUpdateString,
  WtClnSetFromAny
};
